/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.tchepannou.limbe.web;

import com.tchepannou.limbe.Context;
import com.tchepannou.limbe.annotation.Action;
import com.tchepannou.limbe.helper.ClassHelper;
import com.tchepannou.limbe.impl.ActionControllerWrapperResolverImpl;
import com.tchepannou.limbe.model.Model;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Herve Tchepannou
 */
public class ActionControllerContainer
{
    //-- Static
    private static final Logger LOG = Logger.getLogger (ActionControllerContainer.class.getName ());
    
    //-- Attribute
    private Context _context;
    private Map<String, ActionControllerWrapper> _wrappers = new HashMap<String, ActionControllerWrapper> ();
    private ActionControllerWrapperResolver _resolver;
    private boolean _initialized;
    
    //-- Constructeur
    public ActionControllerContainer ()
    {
    }


    //-- Public method
    public void init (Context context)
    {
        LOG.info ("Initializing");
        _context = context;
        try
        {
            _resolver = new ActionControllerWrapperResolverImpl ();
                
            /* Load the action classes */
            String packageName = _context.getBasePackage () + ".action";
            loadActionControllers (packageName);
            
            /* Initializing */
            for (ActionControllerWrapper wrapper : _wrappers.values ())
            {
                LOG.info ("Initializing " + wrapper);
                wrapper.init (context);
            }

            _initialized = true;
            LOG.info ("Initialized");
        }
        catch (Exception e)
        {
            LOG.log (Level.SEVERE, "Unable to initialized", e);
            throw new IllegalStateException ("Unable to initialized the ActionControllerContainer", e);
        }
    }

    public void destroy ()
    {
        LOG.info ("Destroying");
        for (ActionControllerWrapper wrapper : _wrappers.values ())
        {
            LOG.info ("Destroying " + wrapper);
            wrapper.destroy ();
        }

        _context = null;
        _wrappers.clear ();
        _initialized = false;
        LOG.info ("Destroyed");
    }

    public ActionController service (HttpServletRequest request, HttpServletResponse response)
        throws IOException
    {
        if (!_initialized)
        {
            throw new IllegalStateException ("Not initialized. call init() first");
        }

        /* path */
        String path = request.getPathInfo ();
        if ( path.endsWith ("/") )
        {
            path = path.substring (0, path.length () - 1);
        }

        /* get the wrapper */
        ActionControllerWrapper wrapper = getActionControllerWrapper (path);
        if (wrapper != null)
        {
            /* perform the action */
            return wrapper.service (path, request, response);
        }
        else
        {
            LOG.severe ("No ActionController mapped to " + path);
            response.sendError (404, "No ActionController mapped to " + path);
            return null;
        }
    }

    public boolean isInitialized ()
    {
        return _initialized;
    }

    public ActionControllerWrapper getActionControllerWrapper (String path)
    {
        return _resolver.resolve (path, _wrappers);
    }
    
    public void register (Class<? extends ActionController> clazz)
    {
        ActionControllerWrapper wrapper = new ActionControllerWrapper ();
        wrapper.setControllerClass (clazz);

        Action action = (Action)clazz.getAnnotation (Action.class);
        if (action != null)
        {
            Class<? extends Model> model = action.model ();
            if (model != Model.class)
            {
                wrapper.setModelClass (model);
            }
        }

        LOG.info ("Registering " + wrapper);
        String name = ActionController.getName (clazz);
        _wrappers.put (name.toLowerCase (), wrapper);
    }


    //-- Private
    @SuppressWarnings("unchecked")
    private void loadActionControllers (String packageName)
        throws ClassNotFoundException,
               IOException
    {
        LOG.info("Loading Action classes from package: " + packageName);
        
        ClassHelper.Filter filter = createClassFilter(packageName);
        List<Class<?>> classes = ClassHelper.loadClasses(_context.getServletContext(), filter);
        LOG.info (classes.size() + " classes(s) loaded");
        
        for (Class<?> clazz : classes)
        {
            register ((Class<ActionController>)clazz);
        }
    }
    
    private ClassHelper.Filter createClassFilter (final String packageName)
    {
        return new ClassHelper.Filter()
        {
            @Override
            public boolean accept(Class<?> clazz)
            {
                int modifier = clazz.getModifiers ();
                if (Modifier.isPublic (modifier) 
                    && !Modifier.isAbstract (modifier)
                    && clazz.getPackage ().getName ().startsWith (packageName) 
                    && ActionController.class.isAssignableFrom (clazz) )
                {
                    return true;
                }
                return false;
            }
        };
    }
}
